///////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2009. Dimarik
//
///////////////////////////////////////////////////////////////////////////////

package ru.goodcore.utils.swf
{

import flash.geom.Rectangle;
import flash.utils.ByteArray;
import flash.utils.Endian;

import ru.goodcore.utils.traceByteArray;


/**
 *  @author				Dimarik
 *  @version			1.1
 *  @langversion		3.0
 *  @playerversion		9.0
 *
 */
public class SWFByteArray extends ByteArray {

	/**
	 * @private
	 */
	private static const TAG_SWF:				String = 'FWS';	

	/**
	 * @private
	 */
	private static const TAG_SWF_COMPRESSED:	String = 'CWS';

	/**
	 * @private
	 */
	private static const DEFAULT_VERSION:		uint = 9;

	//---------------------------------------------------------------------
	//
	//   Constructor
	//
	//---------------------------------------------------------------------

	/**
	 * Constructor
	 *  
	 * @param data ByteArray
	 * 
	 */
	public function SWFByteArray(data:ByteArray=null) {
		super();
		super.endian = Endian.LITTLE_ENDIAN;

		var endian:	String;
		var tag:	String;

		if (data) {
			endian = data.endian;
			data.endian = Endian.LITTLE_ENDIAN;

			if (data.bytesAvailable > 26) {
				tag = data.readUTFBytes(3);
				
				if (tag == SWFByteArray.TAG_SWF || tag == SWFByteArray.TAG_SWF_COMPRESSED) {
					this._signature = tag;
					this._version = data.readUnsignedByte();
					data.readUnsignedInt();
					data.readBytes(this);
					if (tag == SWFByteArray.TAG_SWF_COMPRESSED) {
						super.uncompress();
					}
				} else throw new ArgumentError('Error #2124: Loaded file is an unknown type.');
				
				this.readHeader();
			}
			
			data.endian = endian;
		}
		
		else {
			

		}
	}

	//---------------------------------------------------------------------
	//
	//   Properties
	//
	//---------------------------------------------------------------------

	//---------------------------------
	//  signature
	//---------------------------------
	/**
	 * @private 
	 */	
	private var _signature:	String;

	public function get compressed():Boolean {
		return this._signature == TAG_SWF_COMPRESSED;
	}

	public function set compressed(value:Boolean):void {
		this._signature = value ? TAG_SWF_COMPRESSED : TAG_SWF;
	}

	//---------------------------------
	//  version
	//---------------------------------
	/**
	 * @private
	 */
	private var _version:	uint;

	public function get version():uint {
		return this._version;
	}

	public function set version(value:uint):void {
		this._version = value;
	}

	//---------------------------------
	//  frameSize
	//---------------------------------
	/**
	 * @private
	 */
	private var _frameSize:	Rectangle;

	/**
	 * Frame size in twips
	 */
	public function get frameSize():Rectangle {
		return this._frameSize;
	}

	public function set frameSize(value:Rectangle):void {
		this._frameSize = value;
	}

	//---------------------------------
	//  frameRate
	//---------------------------------
	/**
	 * @private
	 */
	private var _frameRate:	Number;

	public function get frameRate():Number {
		return this._frameRate;	
	}

	public function set frameRate(value:Number):void {
		this._frameRate = value;
	}

	//---------------------------------------------------------------------
	//
	//   Private variables
	//
	//---------------------------------------------------------------------

	/**
	 * @private
	 */
	private var _bitIndex:			uint;

	/**
	 * @private
	 */
	private var _position_doABC:	uint;

	//---------------------------------------------------------------------
	//
	//   Public methods
	//
	//---------------------------------------------------------------------

	/**
	 * 
	 * @param value
	 * 
	 */	
	public function importBitmap(value:ByteArray, qname:QName, width:uint, height:uint):void {
		super.position = 0;

		// Signature bytes
		super.writeUTFBytes(TAG_SWF);

		// Single byte file version
		super.writeByte(DEFAULT_VERSION);

		// Length of entire file in bytes. Will be updated later.
		super.writeUnsignedInt(0);

		// Frame Size
		this.writeBytesFromString('30 0A 00 A0 00 0C 01 00');

		// File attrib & bg
		this.writeBytesFromString('44 11 08 00 00 00 43 02 FF FF FF');

		// DefineSceneAndFrameLabelData
		this.writeBytesFromString('BF 15 0B 00 00 00 01 00 53 63 65 6E 65 20 31 00 00');

		var baDefineBitsLossless:ByteArray = new ByteArray();
		baDefineBitsLossless.endian = Endian.LITTLE_ENDIAN;

		// DefineBitsLossless
		var tag:uint = 20;
		baDefineBitsLossless.writeShort(0);
		// length
		baDefineBitsLossless.writeUnsignedInt(0);

		// CharacterID
		baDefineBitsLossless.writeShort(1);

		// BitmapFormat: 5 = 24-bit RGB image
		baDefineBitsLossless.writeByte(5);

		// BitmapWidth
		baDefineBitsLossless.writeShort(width);

		// BitmapHeight
		baDefineBitsLossless.writeShort(height);

		//if (If BitmapFormat = 3, write BitmapColorTableSize, UI8)

		baDefineBitsLossless.writeBytes(value);

		// write tag length
		baDefineBitsLossless.position = 0;
		tag <<= 6;
		tag |= 0x3F;
		baDefineBitsLossless.writeShort(tag);

		baDefineBitsLossless.writeUnsignedInt(baDefineBitsLossless.length - 6);

		//trace(traceByteArray(baDefineBitsLossless));
		
		// 
		var baDoABC:ByteArray = new ByteArray();
		baDoABC.endian = Endian.LITTLE_ENDIAN;

		// DoABC
		baDoABC.writeShort(0);
		// length
		baDoABC.writeUnsignedInt(0);

		// Flags
		baDoABC.writeUnsignedInt(1);

		// Bytecode name
		baDoABC.writeByte(0);

		// u16 minor_version
		baDoABC.writeShort(0x10);

		//u16 major_version
		baDoABC.writeShort(0x2e);
		
		baDoABC.writeByte(0x00);
		baDoABC.writeByte(0x00);
		baDoABC.writeByte(0x00);
		baDoABC.writeByte(0x09);
		baDoABC.writeByte(0x00);
		// 
		baDoABC.writeByte(0x06);
		baDoABC.writeUTFBytes('Number');
		
		// package name len
		if (qname.uri) {
			baDoABC.writeByte(qname.uri.length);
			baDoABC.writeUTFBytes(qname.uri);
		}
		
		// class name len
		baDoABC.writeByte(qname.localName.length);
		baDoABC.writeUTFBytes(qname.localName);
		
		// 
		baDoABC.writeByte(0x0D);
		baDoABC.writeUTFBytes('flash.display');

		baDoABC.writeByte(0x0A);
		baDoABC.writeUTFBytes('BitmapData');
		
		var len:uint = qname.localName.length + 1;

		if (qname.uri) {
			len += qname.uri.length;
		}
		// full qualified class name len
		baDoABC.writeByte(len);
		if (qname.uri) {
			baDoABC.writeUTFBytes(qname.uri + ':');
		}
		baDoABC.writeUTFBytes(qname.localName);
		
		// 
		baDoABC.writeByte(0x06);
		baDoABC.writeUTFBytes('Object');

		// DoABC method bodie
		this.writeBytesFromString('                                       05 16 01', baDoABC);
		this.writeBytesFromString('16 03 16 05 18 07 00 05 07 01 02 07 02 04 07 03', baDoABC);
		this.writeBytesFromString('06 07 01 08 03 00 00 00 00 02 00 01 01 00 00 00', baDoABC);
		this.writeBytesFromString('00 00 00 00 01 02 03 08 04 00 01 00 00 00 01 02', baDoABC);
		this.writeBytesFromString('01 02 04 01 00 03 00 01 01 04 05 03 D0 30 47 00', baDoABC);
		this.writeBytesFromString('00 01 03 03 05 06 08 D0 30 D0 D1 D2 49 02 47 00', baDoABC);
		this.writeBytesFromString('00 02 02 01 01 04 13 D0 30 65 00 60 04 30 60 03', baDoABC);
		this.writeBytesFromString('30 60 03 58 00 1D 1D 68 02 47 00 00',             baDoABC);

		// write tag length
		baDoABC.position = 0;
		tag = 82;
		tag <<= 6;
		tag |= 0x3F;
		baDoABC.writeShort(tag);

		baDoABC.writeUnsignedInt(baDoABC.length - 6);

		this.writeBytes(baDoABC);

		this.writeBytes(baDefineBitsLossless);
		
		// SymbolClass
		// 
		var baSymbolClass:ByteArray = new ByteArray();
		baSymbolClass.endian = Endian.LITTLE_ENDIAN;
		baSymbolClass.writeShort(0);
		// length
		baSymbolClass.writeUnsignedInt(0);

		// NumSymbols UI16
		baSymbolClass.writeShort(1);

		// Tag1 UI16
		baSymbolClass.writeShort(1);

		// Name1 STRING
		if (qname.uri) {
			baSymbolClass.writeUTFBytes(qname.uri + '.');
		}
		baSymbolClass.writeUTFBytes(qname.localName);

		// write tag length
		baSymbolClass.position = 0;
		tag = 76;
		tag <<= 6;
		tag |= 0x3F;
		baSymbolClass.writeShort(tag);

		baSymbolClass.writeUnsignedInt(baSymbolClass.length - 5);
		
		this.writeBytes(baSymbolClass);
		

		this.writeBytesFromString('00 40 00 00 00');
		
		
		// Entire filelength
		this.position = 4;
		this.writeUnsignedInt(this.length);
		
		//trace( traceByteArray(this) );
	}

	public function writeBytesFromString(bytesHexString:String, ba:ByteArray=null):void {
		bytesHexString = bytesHexString.replace(/\s/g, '');

		var length:uint = bytesHexString.length;
		
		if (!ba)
			ba = this;

		for (var i:uint = 0; i < length; i += 2) {
			var hexByte:String = bytesHexString.substr(i, 2);
			var byte:uint = parseInt(hexByte, 16);
			ba.writeByte(byte);
		}
	}

	/**
	 * Writes Rectangle record into bytearray
	 * all dimensions in twips (1/20 of pixel)
	 */
	public function setRect(xMin:int, xMax:int, yMin:int, yMax:int):void {
		var max:Number = Math.max.apply(null, arguments);
		var bitsNum:uint = max.toString(2).length + 1;

		var min:Number = Math.min.apply(null, arguments);
		if (min < 0) {
			bitsNum = Math.max(bitsNum, min.toString(2).length);
		}
	
		if (bitsNum > 5) {
			throw new ArgumentError('Rectangle too wide');
		}

		this.writeBits(bitsNum, 5, 0);
		this.writeBits(xMin, 	bitsNum);
		this.writeBits(xMax, 	bitsNum);
		this.writeBits(yMin, 	bitsNum);
		this.writeBits(yMax, 	bitsNum);

	}


	public function writeBits(bits:Number, length:uint, start:int = -1):Number {
		if (start < 0) start = this._bitIndex;
		this._bitIndex = start;

		var byte:					uint = this[super.position];
		var out:					Number = 0;

		var currentByteBitsLeft:	uint = 8 - start;
		var bitsLeft:				Number = length - currentByteBitsLeft;

		if (bitsLeft > 0) {
			super.position++;
			out = this.writeBits(bits, bitsLeft, 0) | ((byte & ((1 << currentByteBitsLeft) - 1)) << (bitsLeft));
		} else {
			out = (byte >> (8 - length - start)) & ((1 << length) - 1);
			this._bitIndex = (start + length) % 8;
			if (start + length > 7) super.position++;
		}

		return out;
	}

	public function readBits(length:uint, start:int = -1):Number {
		if (start < 0) start = this._bitIndex;
		this._bitIndex = start;
		var byte:uint = this[super.position];
		var out:Number = 0;
		var shift:Number = 0;
		var currentByteBitsLeft:uint = 8 - start;
		var bitsLeft:Number = length - currentByteBitsLeft;
		
		if (bitsLeft > 0) {
			super.position++;
			out = this.readBits(bitsLeft, 0) | ((byte & ((1 << currentByteBitsLeft) - 1)) << (bitsLeft));
		} else {
			out = (byte >> (8 - length - start)) & ((1 << length) - 1);
			this._bitIndex = (start + length) % 8;
			if (start + length > 7) super.position++;
		}
		
		return out;
	}

	public function readRect():Rectangle {
		var pos:	uint = super.position;
		var byte:	uint = this[pos];
		var bits:	uint = byte >> 3;
		var xMin:	Number = this.readBits(bits, 5) / 20;
		var xMax:	Number = this.readBits(bits) / 20;
		var yMin:	Number = this.readBits(bits) / 20;
		var yMax:	Number = this.readBits(bits) / 20;
		super.position = pos + Math.ceil(((bits * 4) - 3) / 8) + 1;
		return new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin);
	}


	public function traceArray(value:ByteArray):String { // for debug
		return traceByteArray(value);
	}

	//---------------------------------------------------------------------
	//
	//   Private methods
	//
	//---------------------------------------------------------------------

	/**
	 * @private
	 */
	private function readFrameRate():void {
		if (this._version < 8) {
			this._frameRate = super.readUnsignedShort();
		} else {
			var fixed:Number = super.readUnsignedByte() / 0xFF;
			this._frameRate = super.readUnsignedByte() + fixed;
		}
	}

	/**
	 * @private
	 */
	private function readHeader():void {
		this._frameSize = this.readRect();
		this.readFrameRate();		
		super.readShort(); // num of frames
	}

}
}


//=============================================================================
//
//   Innner definitions
//
//=============================================================================

import flash.utils.ByteArray;

internal class SWFHeader {

	//-------------------------------------------------------------------------
	//
	//   Constructor
	//
	//-------------------------------------------------------------------------

	public function SWFHeader(data:ByteArray) {
		
	}

}